<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="../../../resources/js-test.js"></script>
<title></title>
</head>
<body>
<p id=description></p>
<div id="console"></div>
<script>

window.jsTestIsAsync = true;
var mutations;
var mutations2;
var div;
var subDiv, subDiv2, subDiv3, text;
var calls;

function testBasic() {
    var observer;

    function start() {
        debug('Testing basic aspects of subtree observation.');

        mutations = null;
        div = document.createElement('div');
        subDiv = div.appendChild(document.createElement('div'));
        subDiv.innerHTML = 'hello, world';
        observer = new MutationObserver(function(mutations) {
            window.mutations = mutations;
        });

        observer.observe(div, {attributes: true, characterData: true, subtree: true});
        subDiv.setAttribute('foo', 'bar');
        subDiv.firstChild.textContent = 'goodbye!';
        setTimeout(finish, 0);
    }

    function finish() {
        debug('...attribute and characterData changes in subtree');

        shouldBe('mutations.length', '2');
        shouldBe('mutations[0].type', '"attributes"');
        shouldBe('mutations[0].target', 'subDiv');
        shouldBe('mutations[0].attributeName', '"foo"');
        shouldBe('mutations[0].attributeNamespace', 'null');
        shouldBe('mutations[1].type', '"characterData"');
        shouldBe('mutations[1].target', 'subDiv.firstChild');
        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testMultipleObservers() {
    var observer;
    var observer2;

    function start() {
        debug('Testing two observers at different depths.');

        mutations = null;
        mutations2 = null;
        div = document.createElement('div');
        subDiv = div.appendChild(document.createElement('div'));
        observer = new MutationObserver(function(mutations) {
            window.mutations = mutations;
        });
        observer2 = new MutationObserver(function(mutations) {
            window.mutations2 = mutations;
        });

        observer.observe(div, {attributes: true, subtree: true});
        observer2.observe(subDiv, {attributes: true});
        subDiv.setAttribute('foo', 'bar');
        setTimeout(finish, 0);
    }

    function finish() {
        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"attributes"');
        shouldBe('mutations[0].target', 'subDiv');
        shouldBe('mutations[0].attributeName', '"foo"');
        shouldBe('mutations[0].attributeNamespace', 'null');
        shouldBe('mutations2.length', '1');
        shouldBe('mutations2[0].type', '"attributes"');
        shouldBe('mutations2[0].target', 'subDiv');
        shouldBe('mutations2[0].attributeName', '"foo"');
        shouldBe('mutations2[0].attributeNamespace', 'null');
        observer.disconnect();
        observer2.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testMultipleObservations() {
    var observer;

    function start() {
        debug('Testing one observer at two different depths.');

        mutations = null;
        calls = 0;
        div = document.createElement('div');
        subDiv = div.appendChild(document.createElement('div'));
        observer = new MutationObserver(function(mutations) {
            window.mutations = mutations;
            ++calls;
        });

        observer.observe(div, {attributes: true, subtree: true});
        observer.observe(subDiv, {attributes: true, subtree: true});
        subDiv.setAttribute('foo', 'bar');
        setTimeout(finish, 0);
    }

    function finish() {
        shouldBe('calls', '1');
        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"attributes"');
        shouldBe('mutations[0].target', 'subDiv');
        shouldBe('mutations[0].attributeName', '"foo"');
        shouldBe('mutations[0].attributeNamespace', 'null');
        observer.disconnect();
        debug('');
        runNextTest();
    }

    start();
}

function testTransientDetachedBasic() {
    var observer;

    function start() {
        debug('Testing that transiently detached nodes are still observed via subtree.');

        mutations = null;
        div = document.createElement('div');
        subDiv = div.appendChild(document.createElement('div'));
        subDiv.innerHTML = 'hello, world';
        observer = new MutationObserver(function(mutations) {
            window.mutations = mutations;
        });

        observer.observe(div, {attributes: true, characterData: true, subtree: true});
        subDiv.setAttribute('foo', 'bar');
        div.removeChild(subDiv);
        subDiv.setAttribute('test', 'test');
        setTimeout(checkDeliveredAndChangeAgain, 0);
    }

    function checkDeliveredAndChangeAgain() {
        debug('...both changes should be received. Change detached subDiv again.');

        shouldBe('mutations.length', '2');
        shouldBe('mutations[0].type', '"attributes"');
        shouldBe('mutations[0].target', 'subDiv');
        shouldBe('mutations[0].attributeName', '"foo"');
        shouldBe('mutations[1].type', '"attributes"');
        shouldBe('mutations[1].target', 'subDiv');
        shouldBe('mutations[1].attributeName', '"test"');

        mutations = null;
        subDiv.setAttribute('foo', 'baz');

        setTimeout(checkNotDeliveredAndReattach);
    }

    function checkNotDeliveredAndReattach() {
        debug('...transient subtree observation was stopped after delivery, so subDiv change should not be received. Reattach and change again.');

        shouldBe('mutations', 'null');

        mutations = null
        div.appendChild(subDiv);
        subDiv.setAttribute('foo', 'bat');

        setTimeout(checkDeliveredAndReobserve);
    }

    function checkDeliveredAndReobserve() {
        debug('...reattached subtree should now be observable. Try detaching and re-observing.');

        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"attributes"');
        shouldBe('mutations[0].target', 'subDiv');
        shouldBe('mutations[0].attributeName', '"foo"');

        mutations = null;
        div.removeChild(subDiv);
        subDiv.firstChild.textContent = 'badbye';
        observer.observe(div, {attributes: true, characterData: true, subtree: true});
        subDiv.setAttribute('foo', 'boo');

        setTimeout(finish);
    }


    function finish() {
        debug('...The change made before re-observing should be received, but not the one after.');

        shouldBe('mutations.length', '1');
        shouldBe('mutations[0].type', '"characterData"');
        shouldBe('mutations[0].target', 'subDiv.firstChild');

        observer.disconnect();
        debug('');
        runNextTest();
    }
    start();
}

function testTransientDetachedDetailed() {
    var observer;

    function start() {
        debug('Testing correct behavior of transient observation with complex movement .');

        mutations = null;
        div = document.createElement('div');
        subDiv = div.appendChild(document.createElement('div'));
        subDiv2 = subDiv.appendChild(document.createElement('div'));
        subDiv2.innerHTML = 'hello, world';
        subDiv3 = document.createElement('div');

        observer = new MutationObserver(function(mutations) {
            window.mutations = mutations;
        });

        observer.observe(div, {attributes: true, characterData: true, subtree: true});
        div.removeChild(subDiv);
        subDiv.removeChild(subDiv2);
        text = subDiv2.removeChild(subDiv2.firstChild);

        subDiv.setAttribute('a', 'a');
        subDiv2.setAttribute('b', 'b');
        text.textContent = 'c';
        subDiv3.appendChild(subDiv2);
        subDiv3.setAttribute('d', 'd');
        subDiv2.setAttribute('e', 'e');
        div.appendChild(subDiv3);
        subDiv3.setAttribute('f', 'f');
        subDiv2.setAttribute('g', 'g');

        setTimeout(finish, 0);
    }

    function finish() {
        debug('...All changes should be received except for setting the "d" attribute on subDiv3 before it was reachable from div.');

        shouldBe('mutations.length', '6');
        shouldBe('mutations[0].type', '"attributes"');
        shouldBe('mutations[0].target', 'subDiv');
        shouldBe('mutations[0].attributeName', '"a"');

        shouldBe('mutations[1].type', '"attributes"');
        shouldBe('mutations[1].target', 'subDiv2');
        shouldBe('mutations[1].attributeName', '"b"');

        shouldBe('mutations[2].type', '"characterData"');
        shouldBe('mutations[2].target', 'text');

        shouldBe('mutations[3].type', '"attributes"');
        shouldBe('mutations[3].target', 'subDiv2');
        shouldBe('mutations[3].attributeName', '"e"');

        shouldBe('mutations[4].type', '"attributes"');
        shouldBe('mutations[4].target', 'subDiv3');
        shouldBe('mutations[4].attributeName', '"f"');

        shouldBe('mutations[5].type', '"attributes"');
        shouldBe('mutations[5].target', 'subDiv2');
        shouldBe('mutations[5].attributeName', '"g"');

        observer.disconnect();
        debug('');
        runNextTest();
    }
    start();
}

var tests = [testBasic, testMultipleObservers, testMultipleObservations, testTransientDetachedBasic, testTransientDetachedDetailed];
var testIndex = 0;

function runNextTest() {
    if (testIndex < tests.length)
        tests[testIndex++]();
    else
        finishJSTest();
}

description('Test WebKitMutationObserver.observe on a subtree');

runNextTest();
</script>
</body>
</html>
